package met.cs664.ai.cox.soc.strategies;

import met.cs664.ai.cox.soc.strategies.placement.*;
import soc.debug.D;
import soc.game.SOCBoard;
import soc.game.SOCPlayer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Guides the placement of the initial settlements at the beginning of the game. Evaluates all available nodes on the
 * game board over several weighted constraints. Higher priority evaluations are performed first and scored on each node.
 * Subsequent passes augment these scores based on their own constraint updating the weighted value of each nodes.
 * The final pass elects the highest scored candidate location to place a settlement.
 *
 * The constraints used to evaluate nodes are not fixed in order. They are rearranged based on the which position we are in,
 * and if we are placing the second versus first settlement.
 *
 * See http://www.settlers-strategy.com/settlers_of_catan_strategy_placement_1.html for a detailed explanation on
 * placement strategy.
 *
 * @author mcox
 */
public class InitialPlacement {

    /** The game board to evaluate */
    private final SOCBoard board;

    /** Our player data */
    private final SOCPlayer player;

    /** Evaluators ordered and weighted for placing the first settlement */
    private final List<PlacementEvaluator> firstSettlementEvaluators = new ArrayList<PlacementEvaluator>();

    /** Evaluators ordered and weighted for placing the second settlement */
    private final List<PlacementEvaluator> secondSettlementEvaluators = new ArrayList<PlacementEvaluator>();

    /**
     * Initialize evaluators
     * @param board
     * @param player
     * @param isLastPlayer
     */
    public InitialPlacement(SOCBoard board, SOCPlayer player, boolean isLastPlayer) {
        this.board = board;
        this.player = player;

        firstSettlementEvaluators.add(new ResourceProbability(1.0f));
        firstSettlementEvaluators.add(new ResourceFrequency(1.0f));
        firstSettlementEvaluators.add(new PortExpansion(1.0f));
        firstSettlementEvaluators.add(new ResourceSynergy(0.5f));

        // Initial settlement placement is in snake order, and player 4 gets to place both initial settlements right
        // after one another.
        if (isLastPlayer) {
            firstSettlementEvaluators.add(new PlayerFour(10.0f));
        }

        secondSettlementEvaluators.add(new ResourceProbability(1.0f));
        secondSettlementEvaluators.add(new PortExpansion(0.5f));
        secondSettlementEvaluators.add(new ResourceFrequency(0.5f));
    }

    /**
     * Returns the coordinate of where to place our first settlement after scoring placements through evaluators
     * @return the hex coord for our first settlement
     */
    public int electFirstSettlement() {
        List<PlacementCandidate> candidates = getSettlementCandidates();
        for (PlacementEvaluator evaluator : firstSettlementEvaluators) {
            evaluator.evaluateCandidates(board, candidates);
            Collections.sort(candidates);
            D.debug(String.format("Evaluation after %s would have nominated %s", evaluator.toString(),
                    Integer.toHexString(candidates.get(0).getCoord())));
        }

        return candidates.get(0).getCoord();
    }

    /**
     * Returns the coordinate of where to place our second settlement after scoring placements through evaluators
     * @return the hex coord of our second settlement
     */
    public int electSecondSettlement() {
        List<PlacementCandidate> candidates = getSettlementCandidates();
        for (PlacementEvaluator evaluator : secondSettlementEvaluators) {
            evaluator.evaluateCandidates(board, candidates);
            Collections.sort(candidates);
            D.debug(String.format("Evaluation after %s would have nominated %s", evaluator.toString(),
                    Integer.toHexString(candidates.get(0).getCoord())));
        }

        return candidates.get(0).getCoord();
    }

    /**
     * Build and return a list of potential settlement locations based on the most recent board state
     * @return potential settlement locations
     */
    private List<PlacementCandidate> getSettlementCandidates() {
        List<PlacementCandidate> candidates = new ArrayList<PlacementCandidate>();

        for (int i = board.MINNODE_V1; i < board.MAXNODE; ++i) {
            if (player.isPotentialSettlement(i)) {
                candidates.add(new PlacementCandidate(i));
            }
        }

        return candidates;
    }
}
